﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using AddIn.EA.Framework.Common;

namespace AddIn.EA.Framework
{
    /// <summary>
    /// Sealed disposable class that manage the Enterprise Architect repository requests.
    /// <remarks>
    /// This class is first step to access the EA Object Model features with AddIn Framework.
    /// </remarks>
    /// </summary>
    public sealed class RepositoryManager : IDisposable
    {
        #region Fields
        private PackageList _models;
        private bool disposed = false;
        private bool loadByReference = false;
        #endregion

        #region Construtors
        /// <summary>
        /// Get an instance of repository manager
        /// <remarks>
        /// The private constructor declaration to turn the class accessible only for static methods from itself.
        /// </remarks>
        /// </summary>
        private RepositoryManager() { }
        #endregion

        #region Properties
        
        /// <summary>
        /// The list of models.
        /// <remarks>Model is a type of package, because this returns a list of packages.</remarks>
        /// </summary>
        public PackageList Models
        {
            get
            {
                if (_models == null)
                    _models = new PackageList(Repository.Models);

                return _models;
            }
        }

        /// <summary>
        /// Project ID
        /// </summary>
        public string ProjectGUID
        {
            get
            {
                return Repository.ProjectGUID; 
            }
        }

        /// <summary>
        /// The reference of EA Object Model repository
        /// </summary>
        private EA_Application.Repository Repository { get; set; }
        
        #endregion

        #region Public Static Methods
        /// <summary>
        /// Create a new EAP(Enterprise Architect Project) file.
        /// </summary>
        /// <param name="filePath">The path and file name where will be created the EAP file.</param>
        /// <returns>if the file was created.</returns>
        public static bool CreateNewRepository(string filePath)
        {
            FileInfo file = new FileInfo(filePath);

            if (file.Exists)
                throw new FileLoadException(string.Format("The file {0} already exists!", file.Name));
            else if (file.DirectoryName.Trim().Length == 0)
                throw new DirectoryNotFoundException("It wasn't informed the directory!");

            EA_Application.Repository repository = new EA_Application.RepositoryClass();
            bool result = repository.CreateModel(EA_Application.CreateModelType.cmEAPFromBase, filePath, 0);
            repository.Exit();

            return result;
        }
        /// <summary>
        /// Open a EAP(Enterprise Architect Project) file.
        /// </summary>
        /// <remarks>
        /// Used to open a repository of a specific file. Be careful with the context or scope used in this case 
        /// because if it was left out, probably the file will be closed by Dispose Method when the Garbage Collector
        /// be executed.
        /// </remarks>
        /// <example>
        /// <code>
        /// public class Application : Form
        /// {
        ///     private RepositoryManager _repository;
        ///     
        ///     public Application()
        ///     {
        ///         _repository = RepositoryManager.OpenFile("C:\\Application.EAP");
        ///     }
        ///     
        ///     // Others implementations
        ///     
        ///     protected override void OnClosed(EventArgs e)
        ///     {
        ///         _repository.Close();
        ///         base.OnClosed(e);
        ///     }
        /// }
        /// </code>
        /// </example>
        /// <param name="filePath">The path and file name where find the EAP file.</param>
        /// <returns>The object RepositoryManager</returns>
        public static RepositoryManager OpenRepository(string filePath)
        {
            EA_Application.Repository repository = new EA_Application.RepositoryClass();
            RepositoryManager manager = null;

            if (repository.OpenFile(filePath))
            {
                manager = new RepositoryManager();
                manager.Repository = repository;
            }

            return manager;
        }

        /// <summary>
        /// Load the RepositoryManager in edition mode. 
        /// <remarks>
        /// Used if you have an instance of EA_Application.Repository 
        /// and cannot close the Enterprise Architeture at the end of implementation.
        /// </remarks>
        /// <code>
        /// public void Execute(EA_Application.Repository repository)
        /// {
        ///     using(RepositoryManager manager = RepositoryManager.LoadManager(repository))
        ///     {
        ///         // Others implementations
        ///     }        
        /// }
        /// </code>
        /// </summary>
        /// <param name="repository">The Repository class of EA Object Model</param>
        /// <returns>The object RepositoryManager</returns>
        public static RepositoryManager LoadManager(EA_Application.Repository repository)
        {
            RepositoryManager manager = new RepositoryManager();
            manager.Repository = repository;
            manager.loadByReference = true;

            return manager;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Find a model by name.
        /// </summary>
        /// <param name="name">The model name.</param>
        /// <returns>The model found.</returns>
        public Package GetModelByName(string name)
        {
            return this.Models.GetReferenceByName(name);
        }
        /// <summary>
        /// Find a package by name in recursive mode.
        /// </summary>
        /// <param name="name">The package name.</param>
        /// <returns>The package found.</returns>
        public Package GetPackageByName(string name)
        {
            return this.Models.GetReferenceByName(name, true);
        }
        /// <summary>
        /// Find a package by id in recursive mode.
        /// </summary>
        /// <param name="name">The package di.</param>
        /// <returns>The package found.</returns>
        public Package GetPackageById(int id)
        {
            return this.Models.GetReferenceById(id, true);
        }

        /// <summary>
        /// Get the selected object on treeview of the Project Browser in EA.
        /// </summary>
        /// <returns>The element selected.</returns>
        public IElement GetSelectedObjectOnTreeView()
        {
            object reference;
            EA_Application.ObjectType type = this.Repository.GetTreeSelectedItem(out reference);

            if (reference != null)
            {
                EA_Application.Element element;
                Element parent;
                switch (type)
                {
                    case EA_Application.ObjectType.otModel:
                    case EA_Application.ObjectType.otPackage:
                        return this.GetPackageById(((EA_Application.Package)reference).PackageID);
                    case EA_Application.ObjectType.otElement:
                        return new Element((EA_Application.Element)reference, this.GetPackageById(((EA_Application.Element)reference).ParentID));
                    case EA_Application.ObjectType.otAttribute:
                        element = this.Repository.GetElementByID(((EA_Application.Attribute)reference).ParentID);
                        parent = new Element(element, GetPackageById(element.ParentID));
                        return new AttributeClass((EA_Application.Attribute)reference, parent);
                    case EA_Application.ObjectType.otMethod:
                        element = this.Repository.GetElementByID(((EA_Application.Method)reference).ParentID);
                        parent = new Element(element, GetPackageById(element.ParentID));
                        return new Method((EA_Application.Method)reference, parent);
                    default:
                        return null;
                }
            }
            else
                return null;
        }

        /// <summary>
        /// Get the selected object of EA.
        /// </summary>
        /// <returns>The element selected.</returns>
        public IElement GetSelectedObject()
        {
            object reference;
            EA_Application.ObjectType type = this.Repository.GetContextItem(out reference);

            if (reference != null)
            {
                EA_Application.Element eaElement;
                Element parent;
                switch (type)
                {
                    case EA_Application.ObjectType.otModel:
                    case EA_Application.ObjectType.otPackage:
                        return this.GetPackageById(((EA_Application.Package)reference).PackageID);
                    case EA_Application.ObjectType.otElement:
                        eaElement = this.Repository.GetElementByID(((EA_Application.Element)reference).ElementID);
                        return new Element(eaElement, this.GetPackageById(eaElement.ParentID));
                    case EA_Application.ObjectType.otAttribute:
                        EA_Application.Attribute eaAttribute = this.Repository.GetAttributeByGuid(((EA_Application.Attribute)reference).AttributeGUID);
                        eaElement = this.Repository.GetElementByID(eaAttribute.ParentID);
                        parent = new Element(eaElement, GetPackageById(eaElement.ParentID));
                        return new AttributeClass(eaAttribute, parent);
                    case EA_Application.ObjectType.otMethod:
                        EA_Application.Method eaMethod = this.Repository.GetMethodByGuid(((EA_Application.Method)reference).MethodGUID);
                        eaElement = this.Repository.GetElementByID(eaMethod.ParentID);
                        parent = new Element(eaElement, GetPackageById(eaElement.ParentID));
                        return new Method(eaMethod, parent);
                    default:
                        return null;
                }
            }
            else
                return null;
        }

        /// <summary>
        /// Close the EAP file loaded.
        /// </summary>
        public void Close()
        {
            Dispose();
        }

        #region IDisposable Members

        /// <summary>
        /// Dispose the RepositoryManager.
        /// </summary>
        public void Dispose()
        {
            if (!disposed)
            {
                if (IsProjectOpen(this.Repository) && !loadByReference)
                {
                    this.Repository.CloseFile();
                    this.Repository.Exit();
                }
                disposed = true;
            }
        }

        #endregion

        #endregion

        #region Private Methods
        /// <summary>
        /// Check if the project of EAP file is open.
        /// </summary>
        /// <param name="repository">The Repository object of EA Object Model</param>
        /// <returns></returns>
        private bool IsProjectOpen(EA_Application.Repository repository)
        {
            try
            {
                EA_Application.Collection c = repository.Models;
                return true;
            }
            catch
            {
                return false;
            }
        }

        ~RepositoryManager()
        {
            Dispose();
        }
        #endregion
    }
}
